﻿using EditoneLib;
using EditoneLib.Annotations;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Windows.Foundation.Metadata;

namespace EditoneLibRT
{
    [Activatable(1)]
    public sealed class Track
    {
        internal readonly dynamic DyTrack;
        internal readonly Project Project;
        internal readonly ITrack StTrack;
        private static readonly Type AudioTrackType = typeof (BaseAudioTrack<>);
        private static readonly Type TrackType = typeof (BaseTrack);
        private IDictionary<long, Clip> _clips = new Dictionary<long, Clip>();
        private readonly Dictionary<long, Clip> _virtualClips = new Dictionary<long, Clip>();

        internal Track(Project project, ITrack track)
        {
            Guid = System.Guid.NewGuid().ToString();
            StTrack = track;
            StTrack.CollectionChanged += StTrackOnCollectionChanged;
            if (StTrack.Count > 0)
            {
                StTrackOnCollectionChanged(track, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
            StTrack.PropertyChanged += StTrackOnPropertyChanged;
            DyTrack = track;
            Project = project;

            Height = 160;

            DetectNumChannels();
        }

        public event EventHandler<string> PropertyChanged;

        public double Balance
        {
            get
            {
                if (Type != "StereoTrack")
                {
                    throw new InvalidOperationException("Balance is only defined for StereoTrack.");
                }
                return DyTrack.Balance;
            }

            set
            {
                if (Type != "StereoTrack")
                {
                    throw new InvalidOperationException("Balance is only defined for StereoTrack.");
                }
                if (Equals(DyTrack.Balance, value)) return;
                DyTrack.Balance = (float) value;
                OnPropertyChanged();
            }
        }

        public IDictionary<long, Clip> Clips
        {
            get { return _clips; }
            private set
            {
                if (Equals(value, _clips)) return;
                _clips = value;
                OnPropertyChanged();
            }
        }

        public string Color
        {
            get { return Enum.GetName(typeof (TrackColor), StTrack.Color); }
            set
            {
                var color = (TrackColor) Enum.Parse(typeof (TrackColor), value);
                if (Equals(color, StTrack.Color)) return;
                StTrack.Color = color;
                OnPropertyChanged();
            }
        }

        public string Guid { get; private set; }

        public double Height { get; set; }

        public bool IsAudioTrack { get; private set; }

        public string Name
        {
            get { return StTrack.Name; }

            set
            {
                if (value == Name) return;
                StTrack.Name = value;
                OnPropertyChanged();
            }
        }

        public int NumChannels { get; private set; }

        public string Type { get; private set; }

        public double Volume
        {
            get
            {
                if (!IsAudioTrack)
                {
                    throw new InvalidOperationException("This is not an audio Track.");
                }
                return DyTrack.Volume;
            }

            set
            {
                if (!IsAudioTrack)
                {
                    throw new InvalidOperationException("This is not an audio Track.");
                }
                if (Equals(DyTrack.Volume, value)) return;
                DyTrack.Volume = (float) value;
                OnPropertyChanged();
            }
        }

        public void AddRtClip(TimeSpan offset, Clip clip)
        {
            if (clip.TrackValue == null) clip.TrackValue = this;

            _virtualClips.Add(offset.Ticks, clip);
            Clips.Add(offset.Ticks, clip);
            OnPropertyChanged("Clips");
        }

        public void RemoveClip(Clip clip)
        {
            var pair = StTrack.FirstOrDefault(kvp => kvp.Value == clip.StClip);
            if (!pair.Equals(default(KeyValuePair<TimeSpan, IClip>)))
            {
                StTrack.Remove(pair.Key);
            }
            else
            {
                var rtPair = _virtualClips.FirstOrDefault(kvp => kvp.Value == clip);
                if (rtPair.Equals(default(KeyValuePair<long, Clip>))) return;
                _virtualClips.Remove(rtPair.Key);
            }
        }

        public void Delete()
        {
            Project.RemoveTrack(this);
        }

        public bool HasClip(Clip clip)
        {
            return Clips.Values.Contains(clip);
        }

        internal void AddClip(TimeSpan offset, IClip clip)
        {
            if (StTrack.FirstOrDefault(kvp => kvp.Value == clip).Equals(default(KeyValuePair<TimeSpan, IClip>)))
            {
                StTrack.Add(offset, clip);
            }

            if (StTrack.ContainsKey(offset)) return;

            Clips.Add(offset.Ticks, new Clip(clip, this));
        }

        private void DetectNumChannels()
        {
            Type type = StTrack.GetType();

            Type = type.Name;

            while (type != TrackType && type != null)
            {
                var typeInfo = type.GetTypeInfo();
                if (typeInfo.IsGenericType &&
                    type.GetGenericTypeDefinition() == AudioTrackType)
                {
                    IsAudioTrack = true;
                    NumChannels = DyTrack.NumChannels;
                    return;
                }
                type = typeInfo.BaseType;
            }
            IsAudioTrack = false;
            NumChannels = 0;
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, propertyName);
        }

        private void StTrackOnCollectionChanged(object sender,
            NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            // TODO: udělej něco jako v Project
            Clips =
                StTrack
                    .ToDictionary(
                        kvp => kvp.Key.Ticks,
                        kvp => new Clip(kvp.Value, this))
                    .Union(_virtualClips)
                    .ToDictionary(
                        kvp => kvp.Key,
                        kvp => kvp.Value
                    );
        }

        private void StTrackOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            OnPropertyChanged(propertyChangedEventArgs.PropertyName);
        }
    }
}